本文同步更新於blog
高階模組不應該依賴低階模組。它們都應該依賴抽象。
抽象不應該依賴細節。細節應該依賴抽象。
目的是把高階模組對低階模組的依賴解耦,
改為高階模組依賴定義出的抽象介面,
而由低階模組去實現介面。
其中的依賴關係被顛倒,使得低階模組依賴於高階的抽象介面。
Def. 發電:泛指從其它種類的能源轉換為電力的過程。
最初我們有一個電力系統,發電系統採用火力發電,
之後再由輸電系統,配給各個用戶。
今天我們想要抽換發電系統,改為風力發電。
卻發現原本的電力系統強耦合於火力發電,不易抽換。
透過定義出抽象的發電介面,我們可以讓電力系統依賴於發電介面,
並讓火力發電、風力發電去實作發電介面。
因為依賴抽象化,未來就更容易抽換發電系統了。
以下提供範例程式碼:
情境:目前電力系統採用火力發電
<?php
namespace App\SOLID\DIP\PowerSystems;
class ThermalPower
{
    public function generatePower()
    {
        return '電力';
    }
}
<?php
namespace App\SOLID\DIP\PowerSystems;
use App\SOLID\DIP\PowerSystems\ThermalPower;
class Program
{
    public function getPower()
    {
        $thermalPower = new ThermalPower();
        return $thermalPower->generatePower();
    }
}
隨著科技發展,現在我們想要改成用風力發電來取代火力發電,
卻發現原本的程式,強耦合在ThermalPower。
(依賴了具體的類別)
我們決定定義一個抽象的介面,
讓程式依賴在介面,並由各個發電方式實作介面。
改變彼此的依賴關係。
需求一:定義抽象介面,改變電力系統與火力發電的依賴關係
<?php
namespace App\SOLID\DIP\PowerSystems\Contracts;
interface PowerGeneratable
{
    public function generatePower();
}
<?php
namespace App\SOLID\DIP\PowerSystems;
use App\SOLID\DIP\PowerSystems\Contracts\PowerGeneratable;
class ThermalPower implements PowerGeneratable
{
    public function generatePower()
    {
        return '電力';
    }
}
<?php
namespace App\SOLID\DIP\PowerSystems;
use App\SOLID\DIP\PowerSystems\Contracts\PowerGeneratable;
class Program
{
    public function getPower(PowerGeneratable $powerGeneration)
    {
        return $powerGeneration->generatePower();
    }
}
(註:現在要用什麼樣的發電方式,會交由客戶端決定)
<?php
namespace Tests\Feature\SOLID\DIP\PowerSystems;
use PHPUnit\Framework\TestCase;
use App\SOLID\DIP\PowerSystems\Program;
use App\SOLID\DIP\PowerSystems\ThermalPower;
class ProgramTest extends TestCase
{
    /**
     * @var Program
     */
    protected $sut;
    protected function setUp(): void
    {
        $this->sut = new Program();
    }
    public function testGetPowerByThermalPower()
    {
        $expected = '電力';
        $powerGeneration = new ThermalPower();
        $actual = $this->sut->getPower($powerGeneration);
        $this->assertEquals($expected, $actual);
    }
}
需求二:新增風力發電
<?php
namespace App\SOLID\DIP\PowerSystems;
use App\SOLID\DIP\PowerSystems\Contracts\PowerGeneratable;
class WindPower implements PowerGeneratable
{
    public function generatePower()
    {
        return '電力';
    }
}
<?php
namespace Tests\Feature\SOLID\DIP\PowerSystems;
use PHPUnit\Framework\TestCase;
use App\SOLID\DIP\PowerSystems\Program;
use App\SOLID\DIP\PowerSystems\ThermalPower;
use App\SOLID\DIP\PowerSystems\WindPower;
class ProgramTest extends TestCase
{
    /**
     * @var Program
     */
    protected $sut;
    protected function setUp(): void
    {
        $this->sut = new Program();
    }
    public function testGetPowerByThermalPower()
    {
        $expected = '電力';
        $powerGeneration = new ThermalPower();
        $actual = $this->sut->getPower($powerGeneration);
        $this->assertEquals($expected, $actual);
    }
    public function testGetPowerByWindPower()
    {
        $expected = '電力';
        $powerGeneration = new WindPower();
        $actual = $this->sut->getPower($powerGeneration);
        $this->assertEquals($expected, $actual);
    }
}
最後附上類別圖比較:
依賴具體的火力發電
依賴抽象的發電介面,並由各個發現方式實作介面
(可以觀察火力發電與風力發電的依賴方向,是不是反轉了呢)
ʕ •ᴥ•ʔ:重讀一次DIP發現重點在於,如何定義出足夠抽象的介面。